package com.arpa.wly.waybill;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.gson.*;
import com.google.gson.internal.LinkedTreeMap;
import org.junit.Test;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author liuxr
 * @date 2021年08月17日 16:46
 */
public class DyWaybill {
    String host = "https://openapi.jinritemai.com";
    String appKey= "6995344980931790370";
    String appSecret = "6788d817-1a16-4acc-b6bd-633261a2a9c4";
//    String accessToken = "e86d6128-6b17-43f3-9547-240064163d87";
//    String refreshToken = "d35b0660-7c1e-436d-8595-00e9d92a8122";
    String accessToken = "38644789-44fc-4dab-84ea-346a700a7173";
    String refreshToken = "5a515784-4ee1-4448-b295-351fa203d990";
    String code = "e7f627f3-586b-45f0-a33c-50773028c9e7";

    /**
     * 获取token
     * @author liuxr
     * @date 2021/8/23 9:10
     */
    @Test
    public void getToken(){
        String method = "token.create";
        long timestamp = System.currentTimeMillis() / 1000;
        Map<String, Object> m = new HashMap<>();
        m.put("code", code);
        m.put("grant_type", "authorization_code");
        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);
        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        String methodPath = method.replace('.', '/');
        String u = host + "/" + methodPath +
                "?method=" + HttpUtil.encodeParams(method, StandardCharsets.UTF_8) +
                "&app_key=" + HttpUtil.encodeParams(appKey, StandardCharsets.UTF_8) +
                "&timestamp=" + HttpUtil.encodeParams(Long.toString(timestamp), StandardCharsets.UTF_8) +
                "&v=" + HttpUtil.encodeParams("2", StandardCharsets.UTF_8) +
                "&sign=" + HttpUtil.encodeParams(signVal, StandardCharsets.UTF_8);
        Map<String,Object> params = new HashMap<>();
        params.put("param_json",paramJson);
        String responseVal =  HttpUtil.post(u,params);
        System.out.println("response_val:" + responseVal);
        JSONObject temp = JSONUtil.parseObj(responseVal);
        int errNo = (int)temp.get("err_no");
        String message = (String)temp.get("message");
        System.out.println(message);
        JSONObject data = (JSONObject) temp.get("data");
        int expires_in = (int)data.get("expires_in");
        int shop_id = (int)data.get("shop_id");
        System.out.println(shop_id);

        System.out.println("失效日期："+DateUtil.offset(DateUtil.date(), DateField.SECOND, expires_in));
    }
    @Test
    public void refreshToken(){
        String method = "token.refresh";
        long timestamp = System.currentTimeMillis() / 1000;
        Map<String, Object> m = new HashMap<>();
        m.put("refresh_token", refreshToken);
        m.put("grant_type", "refresh_token");
        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);
        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        String methodPath = method.replace('.', '/');
        String u = host + "/" + methodPath +
                "?method=" + HttpUtil.encodeParams(method, StandardCharsets.UTF_8) +
                "&app_key=" + HttpUtil.encodeParams(appKey, StandardCharsets.UTF_8) +
                "&timestamp=" + HttpUtil.encodeParams(Long.toString(timestamp), StandardCharsets.UTF_8) +
                "&v=" + HttpUtil.encodeParams("2", StandardCharsets.UTF_8) +
                "&sign=" + HttpUtil.encodeParams(signVal, StandardCharsets.UTF_8);
        Map<String,Object> params = new HashMap<>();
        params.put("param_json",paramJson);
        String responseVal =  HttpUtil.post(u,params);
        JSONObject temp = JSONUtil.parseObj(responseVal);
        JSONObject data = (JSONObject) temp.get("data");
        int expires_in = (int)data.get("expires_in");

        System.out.println("失效日期："+DateUtil.offset(DateUtil.date(), DateField.SECOND, expires_in));
        System.out.println("response_val:" + responseVal);
    }
    /**
     * 返回打印 params参数
     * access_token=值&app_key=值&method=logistics.getShopKey&param_json={}&timestamp=时间&v=2&sign=值，
     * 此处只用关心入参，不用关心出参，这个是打印组件调用的，各参数值的生成详见电商开放平台https://op.jinritemai.com/docs/guide-docs/10/23，其中的method=logistics.getShopKey，param_json={}
     * @author liuxr
     * @date 2021/8/17 16:34
     */
    @Test
    public void getPrintParam(){
        String method = "logistics.getShopKey";

        long timestamp = System.currentTimeMillis() / 1000;
        Map<String, Object> m = new HashMap<>();
        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);
        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        System.out.println("access_token="+accessToken+"&app_key="+appKey+"&method=logistics.getShopKey&param_json={}&timestamp="+timestamp+"&v=2&sign=" + signVal);

    }



    @Test
    public void customTemplateList(){
        String method = "logistics.customTemplateList";

        long timestamp = System.currentTimeMillis() / 1000;
        Map<String, Object> m = new HashMap<>();
        m.put("logistics_code", "yunda");

        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);

        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        System.out.println("sign_val:" + signVal);

        // 发起请求
        String responseVal = null;
        try {
            responseVal = fetch(appKey, host, method, timestamp, paramJson, accessToken, signVal);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("response_val:" + responseVal);
    }

    @Test
    public void templateList(){
        String method = "logistics.templateList";
        long timestamp = System.currentTimeMillis() / 1000;



        Map<String, Object> m = new HashMap<>();
        m.put("logistics_code", "yunda");

        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);

        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        System.out.println("sign_val:" + signVal);

        // 发起请求
        String responseVal = null;
        try {
            responseVal = fetch(appKey, host, method, timestamp, paramJson, accessToken, signVal);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("response_val:" + responseVal);
    }

    /**
     * 通过运单号获取打印信息
     * @author liuxr
     * @date 2021/8/17 16:32
     */
    @Test
    public void waybillApply(){
        String method = "logistics.waybillApply";

        long timestamp = System.currentTimeMillis() / 1000;

        Map<String, Object> waybill = new HashMap<>();
        waybill.put("logistics_code", "yunda");
        waybill.put("track_no", "318250428196093");
        List<Map<String, Object>> waybill_applies = new ArrayList<>();
        waybill_applies.add(waybill);
        Map<String, Object> m = new HashMap<>();
        m.put("waybill_applies",waybill_applies);

        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);

        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        System.out.println("sign_val:" + signVal);

        // 发起请求
        String responseVal = null;
        try {
            responseVal = fetch(appKey, host, method, timestamp, paramJson, accessToken, signVal);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("response_val:" + responseVal);
    }

    /**
     * 查询商家和物流商的订购关系以及物流单号使用情况
     * @author liuxr
     * @date 2021/8/17 16:33
     */
    @Test
    public void listShopNetsite(){
        String method = "logistics.listShopNetsite";
        long timestamp = System.currentTimeMillis() / 1000;
        Map<String, Object> m = new HashMap<>();
        m.put("logistics_code", "yuantong");
        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);
        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        System.out.println("sign_val:" + signVal);
        // 发起请求
        String responseVal = null;
        try {
            responseVal = fetch(appKey, host, method, timestamp, paramJson, accessToken, signVal);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("response_val:" + responseVal);
    }

    /**
     * 获取电子面单号
     * @author liuxr
     * @date 2021/8/17 16:33
     */
    @Test
    public void newCreateOrder(){
        //String method = "logistics.newCreateOrder";
        String method = "logistics.newCreateOrder";

        long timestamp = System.currentTimeMillis() / 1000;

        Map<String, Object> address = new HashMap<>();
        address.put("country_code", "CHN");
        address.put("province_name", "山东省");
        address.put("city_name", "临沂市");
        address.put("district_name", "兰山区");
        address.put("detail_address", "临西十二路与聚财七路交汇处罗滨逊智慧物流");

        Map<String, Object> contact = new HashMap<>();
        contact.put("name", "凤凰网凰家尚品");
        contact.put("mobile", "18040589691");

        Map<String, Object> sender_info = new HashMap<>();
        sender_info.put("address", address);
        sender_info.put("contact", contact);


        //收件人地址信息
        Map<String, Object> address2 = new HashMap<>();
        address2.put("country_code", "CHN");
        address2.put("province_name", "山东省");
        address2.put("city_name", "济南市");
        address2.put("district_name", "莱芜区");
        address2.put("detail_address", "上游村小区");

        //收件人联系信息
        Map<String, Object> contact2 = new HashMap<>();
        contact2.put("name", "王子炜");
        contact2.put("mobile", "15244261185");

        //收件人信息
        Map<String, Object> receiver_info = new HashMap<>();
        receiver_info.put("address",address2);
        receiver_info.put("contact", contact2);


        //商品明细列表
        List<Map<String, Object>> items = new ArrayList<>();
        Map<String, Object> item = new HashMap<>();
        item.put("item_name", "粉色加热杯");
        item.put("item_count", "1");
        items.add(item);

        //订单信息
        Map<String, Object> order = new HashMap<>();
        order.put("order_id", "4923640462501875211");
        order.put("receiver_info", receiver_info);
        order.put("items", items);

        //详细订单列表
        List<Map<String, Object>> order_infos = new ArrayList<>();
        order_infos.add(order);

        Map<String, Object> m = new HashMap<>();
        m.put("sender_info", sender_info);
        m.put("logistics_code", "shunfeng");
        m.put("order_infos",order_infos);



        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);

        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        System.out.println("sign_val:" + signVal);

        // 发起请求
        String responseVal = null;
        try {
            responseVal = fetch(appKey, host, method, timestamp, paramJson, accessToken, signVal);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("response_val:" + responseVal);
    }


    // 调用Open Api，取回数据
    public static String fetch(String appKey, String host, String method, long timestamp, String paramJson, String accessToken, String sign) throws IOException {
        String methodPath = method.replace('.', '/');

        String u = host + "/" + methodPath +
                "?method=" + HttpUtil.encodeParams(method, StandardCharsets.UTF_8) +
                "&app_key=" + HttpUtil.encodeParams(appKey, StandardCharsets.UTF_8) +
                "&access_token=" + HttpUtil.encodeParams(accessToken, StandardCharsets.UTF_8) +
                "&timestamp=" + HttpUtil.encodeParams(Long.toString(timestamp), StandardCharsets.UTF_8) +
                "&v=" + HttpUtil.encodeParams("2", StandardCharsets.UTF_8) +
                "&sign=" + HttpUtil.encodeParams(sign, StandardCharsets.UTF_8);

        Map<String,Object> params = new HashMap<>();
        params.put("param_json",paramJson);
        return  HttpUtil.post(u,params);
    }
    // 计算签名
    public static String sign(String appKey, String appSecret, String method, long timestamp, String paramJson) {
        // 按给定规则拼接参数
        String paramPattern = "app_key" + appKey + "method" + method + "param_json" + paramJson + "timestamp" + timestamp + "v2";
        String signPattern = appSecret + paramPattern + appSecret;
        System.out.println("sign_pattern:" + signPattern);

        try {
            return stringToMD5(signPattern);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return hmac(signPattern, appSecret);
    }

    public static String stringToMD5(String plainText) throws UnsupportedEncodingException {
        byte[] secretBytes = null;
        try {
            secretBytes = MessageDigest.getInstance("md5").digest(
                    plainText.getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("没有这个md5算法！");
        }
        StringBuilder md5code = new StringBuilder(new BigInteger(1, secretBytes).toString(16));
        while (md5code.length() < 32) {
            md5code.insert(0, "0");
        }
        return md5code.toString();
    }

    // 计算hmac
    public static String hmac(String plainText, String appSecret) {
        Mac mac;
        try {
            byte[] secret = appSecret.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256");

            mac = Mac.getInstance("HmacSHA256");
            mac.init(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            return "";
        }

        byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
        byte[] digest = mac.doFinal(plainBytes);
        StringBuilder sb = new StringBuilder();
        for (byte b: digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // 序列化参数
// 这一步看上去冗余，实际很重要。如果要自己实现，则必须保证这三点：
// 1、保证JSON所有层级上Key的有序性
// 2、保证JSON的所有数值不带多余的小数点
// 3、保证转义逻辑与这段代码一致
    public static String marshal(Object o) {
        String raw = CustomGson.toJson(o);
        Map<?, ?> m = CustomGson.fromJson(raw, LinkedTreeMap.class); // 执行反序列化，把所有JSON对象转换成LinkedTreeMap
        return CustomGson.toJson(m); // 重新序列化，保证JSON所有层级上Key的有序性
    }

    private static final Gson CustomGson = new GsonBuilder()
            .registerTypeAdapter(LinkedTreeMap.class, newMapSerializer()) // 定制LinkedTreeMap序列化，确保所有key按字典序排序
            .registerTypeAdapter(Integer.class, newNumberSerializer()) // 定制数值类型的序列化，确保整数输出不带小数点
            .registerTypeAdapter(Long.class, newNumberSerializer()) // 同上
            .registerTypeAdapter(Double.class, newNumberSerializer()) // 同上
            .disableHtmlEscaping() // 禁用Html Escape，确保符号不转义：&<>='
            .create();

    // 为LinkedTreeMap定制的序列化器
    public static JsonSerializer<LinkedTreeMap<?, ?>> newMapSerializer() {
        return new JsonSerializer<LinkedTreeMap<?, ?>>() {
            @Override
            public JsonElement serialize(LinkedTreeMap<?, ?> src, Type typeOfSrc, JsonSerializationContext context) {
                List<String> keys = src.keySet().stream().map(Object::toString).sorted().collect(Collectors.toList());
                JsonObject o = new JsonObject();
                for (String k : keys) {
                    o.add(k, context.serialize(src.get(k)));
                }
                return o;
            }
        };
    }

    // 为Number定制化的序列化器
    private static <T extends Number> JsonSerializer<T> newNumberSerializer() {
        return new JsonSerializer<T>() {
            @Override
            public JsonElement serialize(T number, Type type, JsonSerializationContext context) {
                if (number instanceof Integer) {
                    return new JsonPrimitive(number.intValue());
                }
                if (number instanceof Long) {
                    return new JsonPrimitive(number.longValue());
                }
                if (number instanceof Double) {
                    long longValue = number.longValue();
                    double doubleValue = number.doubleValue();
                    if (longValue == doubleValue) {
                        return new JsonPrimitive(longValue);
                    }
                }
                return new JsonPrimitive(number);
            }
        };
    }


    @Test
    public void test(){
        System.out.println(DateUtil.offset(DateUtil.date(), DateField.SECOND, 426533));
    }
}
